<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    #zoom_anchor_cross_on_canvas {
      background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100'><path d='M100 0 L0 100 ' stroke='black' stroke-width='1'/><path d='M0 0 L100 100 ' stroke='black' stroke-width='1'/></svg>") no-repeat center center;
      background-size: 100% 100%, auto;
      border: solid red 2px;
      width: 10px;
      height: 10px;
      transform: translate(-50%, -50%);
      position: absolute;
      left: 0;
      top: 0;
    }

    #zoom_anchor_cross_in_viewport {
      background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100'><path d='M100 0 L0 100 ' stroke='black' stroke-width='1'/><path d='M0 0 L100 100 ' stroke='black' stroke-width='1'/></svg>") no-repeat center center;
      background-size: 100% 100%, auto;
      border: solid green 2px;
      width: 20px;
      height: 20px;
      transform: translate(-50%, -50%);
      position: absolute;
      left: 0;
      top: 0;
    }

    body {
      overflow: hidden;
      width: 100vw;
      height: 100vh;
      margin: 0;
    }

    #control_panel {
      width: 100%;
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0.5;
    }

    #info_panel {
      position: absolute;
      background-color: #A0A0A0;
      bottom: 0;
      right: 0;
      font-family: monospace;
      white-space: pre;
      opacity: 0.5;
    }

    #container {
      user-select: none;
      position: absolute;
      width: 100%;
      height: 100%;
      margin: 0;
    }

    #view_port {
      height: 100%;
      width: 100%;
      overflow: auto;
      position: absolute;
      margin: 0;
      top: 0;
      left: 0;
    }

    #svg_canvas {
      transform-origin: 0 0;
      width: 0;
      height: 0;
      overflow: hidden;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }

    #image {
      position: absolute;
      display: block;
      transform-origin: 0 0;
    }

    #chessboard {
      position: absolute;
      width: 100%;
      height: 100%;
      background: repeating-conic-gradient(#808080 0% 25%, #FFFFFF 0% 50%) 50% / 30px 30px;
      background-position: 0 0;
    }

    #pixel_grid {
      position: absolute;
      width: 100%;
      height: 100%;
      margin: 0;
      background-image: linear-gradient(to right, grey 1px, transparent 1px),
      linear-gradient(to bottom, grey 1px, transparent 1px);
      background-size: 1px 1px;
      mix-blend-mode: difference;
    }

    #image_border {
      position: absolute;
      width: 100%;
      height: 100%;
      border: 1px solid gray;
      mix-blend-mode: difference;
    }
  </style>

  <link type="text/css" href="overlayscrollbars.css" rel="stylesheet" />
  <script type="text/javascript" src="overlayscrollbars.browser.es6.js"></script>

  <link id="scrollbars_style" rel="stylesheet" type="text/css" href="scrollbars.css"/>
  <link id="chessboard_style" rel="stylesheet" type="text/css" href="chessboard.css"/>
  <link id="pixel_grid_style" rel="stylesheet" type="text/css" href="pixel_grid.css"/>

  <title>Image viewer</title>
</head>

<body>
<div id="control_panel" style="display: none; z-index: 30">
  <input type="text" style="width: 100%;" id="url_input" value="http://localhost/image">
  <button onclick="zoomIn()">+</button>
  <button onclick="zoomOut()">-</button>
  <button onclick="fitToViewport()">fit</button>
  <button onclick="setZoom(1)">1:1</button>
  <button onclick="setImageUrl(document.getElementById('url_input').value)">⏎</button>
  <button onclick="reload()">↺</button>
  <button onclick="toggleChessboard()">♔</button>
  <button onclick="toggleGrid()">▦</button>
  <button onclick="toggleBorder()">Borders</button>
  <button onclick="reloadStyles()">Reload styles</button>
</div>

<div id="container">
  <div id="zoom_anchor_cross_in_viewport" style="display: none; z-index: 50;"></div>
  <div id="view_port">
    <div id="svg_canvas">
      <object id="image" style="z-index: 10"></object>
      <div id="chessboard" class="chessboard_base chessboard_style" style="display: none; z-index: 5;"></div>
      <div id="pixel_grid" style="display: none; z-index: 15;"></div>
      <div id="image_border" style="display: none; z-index: 20;"></div>
      <div id="zoom_anchor_cross_on_canvas" style="display: none; z-index: 50;"></div>
    </div>
  </div>
</div>

<div id="info_panel" style="display: none; z-index: 30">No data</div>

<!--Place the script at the end to make sure that the DOM is ready at this point-->
<script>
  const gIsDebug = (new URL(window.location.href).searchParams.get('debug') != null)

  const MIN_GRID_ZOOM = 3
  const MAX_ZOOM = 150
  const MIN_IMAGE_SIZE = 5
  const ViewerStatus = {
    INIT: 'INIT',
    OK: 'OK',
    ERROR: 'ERROR'
  }

  const gState = {
    'zoom': 1.0,
    'grid_visible': false,
    'chessboard_visible': false,
    'status': ViewerStatus.INIT
  }

  let gUrl = ''

  const overlayScrollbars = OverlayScrollbarsGlobal.OverlayScrollbars(document.getElementById('view_port'), {});

  const gUI = {
    'image': document.getElementById('image'),
    'canvas': document.getElementById('svg_canvas'),
    'view_port': overlayScrollbars.elements().viewport,
    'chessboard': document.getElementById('chessboard'),
    'pixel_grid': document.getElementById('pixel_grid'),
    'control_panel': document.getElementById('control_panel'),
    'info_panel': document.getElementById('info_panel'),
    'image_border': document.getElementById('image_border'),
  }

  const gStyles = {
    'scrollbars': document.getElementById('scrollbars_style'),
    'chessboard': document.getElementById('chessboard_style'),
    'pixel_grid': document.getElementById('pixel_grid_style')
  }
  let sendInfo = function (info_string) {}

  function _setError() {
    gState.status = ViewerStatus.ERROR
  }

  function _setImageReady() {
    gState.status = ViewerStatus.OK
  }

  function _updateInfo() {
    let canvas_bbox = gUI.canvas.getBoundingClientRect()
    let info = {
      'status': gState.status,

      'zoom': gState.zoom,
      'viewportSize': {'width': gUI.view_port.clientWidth, 'height': gUI.view_port.clientHeight},
      'imageSize': {'width': gUI.image.clientWidth, 'height': gUI.image.clientHeight},
      'canvasBBox': {'x': canvas_bbox.x, 'y': canvas_bbox.y, 'width': canvas_bbox.width, 'height': canvas_bbox.height},

      'zoomInPossible': gState.zoom < MAX_ZOOM,
      'zoomOutPossible': canvas_bbox.width > MIN_IMAGE_SIZE && canvas_bbox.height > MIN_IMAGE_SIZE,
      'fittedToViewport': Math.abs(gState.zoom - getFitToViewportScale()) < 0.05,
      'realSize': Math.abs(gState.zoom - 1) < 0.05,

      'gridEnabled': gState.grid_visible,
      'chessboardEnabled': gState.chessboard_visible,
    }

    if (gIsDebug) {
      gUI.info_panel.textContent = JSON.stringify(info, null, 2)
    }
    sendInfo(JSON.stringify(info))
  }

  function _setupGrid() {
    if (gState.grid_visible && gState.zoom > MIN_GRID_ZOOM) {
      gUI.pixel_grid.style.backgroundSize = `${gState.zoom}px ${gState.zoom}px`
      gUI.pixel_grid.style.display = ''
    }
    else {
      gUI.pixel_grid.style.display = 'none'
    }
  }

  // Fixes the image, if not possible sets the error message
  function _sanitizeImage() {
    let imageDocument = gUI.image.contentDocument
    if (!imageDocument) {
      _setError()
      return
    }
    let images = imageDocument.getElementsByTagName('svg')
    if (images.length === 0) {
      _setError()
      return
    }

    for (let e of images) {
      let heightAttribute = e.getAttribute('height')
      let widthAttribute = e.getAttribute('width')
      if ((heightAttribute || widthAttribute) && !(heightAttribute?.endsWith('%') || widthAttribute?.endsWith('%'))) continue

      let vb = e.viewBox
      if (!vb || !vb.baseVal) {
        _setError()
      }
      e.setAttribute('width', vb.baseVal.width + 'px')
      e.setAttribute('height', vb.baseVal.height + 'px')
    }

    if (imageDocument.getElementsByTagName('parsererror').length > 0) {
      _setError()
    }
    else {
      _setImageReady()
    }
  }

  let _init = function () {
    gUI.control_panel.style.display = gIsDebug ? '' : 'none'
    gUI.info_panel.style.display = gIsDebug ? '' : 'none'
    document.getElementById('zoom_anchor_cross_on_canvas').style.display = gIsDebug ? '' : 'none'
    document.getElementById('zoom_anchor_cross_in_viewport').style.display = gIsDebug ? '' : 'none'

    gUI.view_port.addEventListener('scroll', () => _updateInfo())

    gUI.image.addEventListener('load', () => {
      _sanitizeImage()
      setZoom(gState.zoom)
    })

    new ResizeObserver(() => setZoom(gState.zoom)).observe(gUI.view_port)
    window.removeEventListener('load', _init)

    _updateInfo()
  }

  window.addEventListener('load', _init)

  function zoomIn() {
    setZoom(gState.zoom * 1.2)
  }

  function zoomOut() {
    setZoom(gState.zoom / 1.2)
  }

  function setZoom(zoom = 1.0, anchor_on_viewport = null) {
    if (anchor_on_viewport == null) {
      anchor_on_viewport = {x: gUI.view_port.offsetWidth / 2, y: gUI.view_port.offsetHeight / 2}
    }
    zoom = Math.min(MAX_ZOOM, zoom)
    if (gUI.image.clientWidth === 0 || gUI.image.clientHeight === 0) {
      // Set some zoom to show the empty image
      zoom = 1
    } else {
      zoom = Math.max(zoom, MIN_IMAGE_SIZE / gUI.image.clientWidth, MIN_IMAGE_SIZE / gUI.image.clientHeight)
    }

    if (Math.abs(zoom - 1.0) < 0.05) zoom = 1.0

    let image_width = gUI.image.clientWidth
    let image_height = gUI.image.clientHeight

    let zoomed_width = image_width * zoom
    let zoomed_height = image_height * zoom

    let anchor_on_canvas = {
      x: anchor_on_viewport.x - gUI.canvas.getBoundingClientRect().x,
      y: anchor_on_viewport.y - gUI.canvas.getBoundingClientRect().y,
    }

    let anchor_on_canvas_scaled = {
      x: anchor_on_canvas.x * (zoom / gState.zoom),
      y: anchor_on_canvas.y * (zoom / gState.zoom)
    }

    gUI.image.style.transform = 'scale(' + zoom + ')'
    _setupCanvas(Math.round(zoomed_width), Math.round(zoomed_height))

    let scroll_to_x = anchor_on_canvas_scaled.x - anchor_on_viewport.x
    let scroll_to_y = anchor_on_canvas_scaled.y - anchor_on_viewport.y

    gUI.view_port.scrollTo(Math.round(scroll_to_x), Math.round(scroll_to_y))

    gState.zoom = zoom

    _setupGrid()
    _updateInfo()

    if (gIsDebug) {
      let cross_on_canvas = document.getElementById('zoom_anchor_cross_on_canvas')
      cross_on_canvas.style.left = anchor_on_canvas_scaled.x + 'px'
      cross_on_canvas.style.top = anchor_on_canvas_scaled.y + 'px'

      let cross_on_viewport = document.getElementById('zoom_anchor_cross_in_viewport')
      cross_on_viewport.style.left = anchor_on_viewport.x + 'px'
      cross_on_viewport.style.top = anchor_on_viewport.y + 'px'
    }
  }

  function _setupCanvas(canvas_width, canvas_height) {
    let translate_x
    let left_pos
    if (canvas_width > gUI.view_port.clientWidth) {
      translate_x = '0'
      left_pos = '0'
    }
    else {
      translate_x = '-50%'
      left_pos = '50%'
    }

    let translate_y
    let top_pos
    if (canvas_height > gUI.view_port.clientHeight) {
      translate_y = '0'
      top_pos = '0'
    }
    else {
      translate_y = '-50%'
      top_pos = '50%'
    }

    gUI.canvas.style.cssText = `width: ${canvas_width}px; height: ${canvas_height}px; left: ${left_pos}; top: ${top_pos}; transform: translate(${translate_x}, ${translate_y});`
  }

  function getFitToViewportScale() {
    let x_scale = (gUI.view_port.clientWidth - 5) / gUI.image.clientWidth
    let y_scale = (gUI.view_port.clientHeight - 5) / gUI.image.clientHeight
    return Math.min(x_scale, y_scale)
  }

  function fitToViewport() {
    setZoom(getFitToViewportScale())
  }

  function setImageUrl(url) {
    if (!url) return

    if (gState.status === ViewerStatus.INIT) {
      let size_observer = new ResizeObserver(() => {
        setZoom(gState.zoom)
      })
      size_observer.observe(gUI.image)

      let remove_observer = function () {
        size_observer.unobserve(gUI.image)
        size_observer.disconnect()
        gUI.image.removeEventListener('load', remove_observer)
      }
      gUI.image.addEventListener('load', remove_observer)
    }

    if (gIsDebug) {
      let url_input = document.getElementById("url_input")
      url_input.value = url
    }
    gUrl = url
    reload()
  }

  function reload() {
    let url = new URL(gUrl)
    url.searchParams.append('timestamp', new Date().getTime().toString())
    gUI.image.data = url.toString()
  }

  function toggleGrid() {
    setGridVisible(!gState.grid_visible)
  }

  function setGridVisible(value) {
    if (gState.grid_visible === value) return
    gState.grid_visible = value
    _setupGrid()
    _updateInfo()
  }

  function toggleChessboard() {
    setChessboardVisible(!gState.chessboard_visible)
  }

  function setChessboardVisible(value) {
    gState.chessboard_visible = value
    gUI.chessboard.style.display = value ? '' : "none"
    _updateInfo()
  }

  function setBorderVisible(value) {
    gUI.image_border.style.display = value ? '' : 'none'
  }

  function isBorderVisible() {
    return gUI.image_border.style.display !== 'none'
  }

  function toggleBorder() {
    setBorderVisible(!isBorderVisible())
  }

  function loadScrollbarsStyle(url) {
    gStyles.scrollbars.href = _setTimestamp(url)
  }

  function loadChessboardStyle(url) {
    gStyles.chessboard.href = _setTimestamp(url)
  }

  function loadPixelGridStyle(url) {
    gStyles.pixel_grid.href = _setTimestamp(url)
  }

  function _setTimestamp(url) {
    let patched_url = new URL(url)
    patched_url.searchParams.set('timestamp', new Date().getTime().toString())
    return patched_url.toString()
  }

  function reloadStyles() {
    loadScrollbarsStyle(gStyles.scrollbars.href)
    loadChessboardStyle(gStyles.chessboard.href)
    loadPixelGridStyle(gStyles.pixel_grid.href)
  }
</script>

</body>
</html>